Category Theory for Programmers 8
Chapter 8, Functoriality
因为函子是范畴的范畴的态射,所以很多关于态射的直觉也适用于函子。双函子(Bifunctor)把两个范畴
class Bifunctor f where
bimap :: (a -> c) -> (b -> d) -> f a b -> f c d
bimap g h = first g . second h
first :: (a -> c) -> f a b -> f c b
first g = bimap g id
second :: (b -> d) -> f a b -> f a d
second = bimap id在实现 Bifunctor 的一个实例时,要不然给出 bimap 的实现,要不然分别给出 first 和 second 的实现,如果同时给出则需要保证联合函子性。
双函子的一个重要例子就是范畴的积和余积:
instance Bifunctor (,) where
bimap f g (x, y) = (f x, g y)
instance Bifunctor Either where
bimap f _ (Left x) = Left (f x)
bimap _ g (Right y) = Right (f y)既然我们已经处理了积和余积,自然地,所有的代数数据类型都可以是函子,只需要对它们进行结构归纳就行。在 Haskell 中,可以这样为代数数据类型自动生成 fmap:
{-# LANGUAGE DeriveFunctor #-}
data Maybe a = Nothing | Just a deriving Functor再回顾 Kleisli 范畴,我们之前提到 Writer 作为 Kleisli 范畴的一个例子,态射是 a -> Writer b,恒等态射是 return :: a -> Writer a,态射的复合用 >=> 定义:
type Writer a = (a, String)
return :: a -> Writer a
return x = (x, "")
(>=>) :: (a -> Writer b) -> (b -> Writer c) -> (a -> Writer c)
f >=> g = \x -> let (y, log1) = f x
(z, log2) = g y
in (z, log1 ++ log2)回顾 fmap 的类型签名:
fmap :: (a -> b) -> (f a -> f b)把 Writer 看成一个 Functor(就像我们看 Maybe 那样),我们期望的 fmap 是:
fmap :: (a -> b) -> (Writer a -> Writer b)神奇的事情是 fmap 可以通过 Kleisli 范畴的恒等态射和态射复合组合出来:
fmap f = id >=> (\x -> return f x)
-- f :: a -> b
-- id :: Writer a -> Writer a
-- \x -> return f x :: Writer a -> Writer b这个有趣的例子或许可以帮助我们窥见 Monad 和 Functor 的关系:Monad 本身就是 Functor(我暂时不保证这个理解是正确的)。
另一个之前提过的函子是 Reader 函子。
type Reader r a = r -> a
instance Functor (Reader r) where
-- fmap :: (a -> b) -> (r -> a) -> (r -> b)
fmap f g = f . g如果固定的是返回值的类型,那么 fmap 的类型签名会变化:
type Op r a = a -> r
-- fmap :: (a -> b) -> (a -> r) -> (b -> r)此时发现根据 fmap 的前两个参数无法构造出 b -> r。我们称之前提到的普通映射对象和态射的函子为协变函子,逆变函子则是:映射对象的方式与协变函子相同,但是在映射态射时,会先反转态射的方向然后再做映射:
type Contravariant f where
contramap :: (b -> a) -> (f a -> f b)
-- Op 就是一个 Contravariant 的实例
instance Contravariant (Op r) where
contramap f g = g . f综上所述,函数箭头运算符在它的第一个参数上是逆变的,在第二个参数上是协变的。如果目标范畴是 Set,这就叫做副函子(Profunctor):
在 Haskell 中:
class Profunctor p where
dimap :: (a -> b) -> (c -> d) -> p b c -> p a d
dimap f g = lmap f . rmap g
lmap :: (a -> b) -> p b c -> p a c
lmap f = dimap f id
rmap :: (b -> c) -> p a b -> p a c
rmap = dimap id(->) 可以看作一个副函子的实例:
instance Profunctor (->) where
dimap ab cd bc = cd . bc . ab
lmap = flip (.)
rmap = (.)hom 函子是 Profunctor 的一个特例。
Challenges
import Data.Bifunctor (Bifunctor(..))
data Pair a b = Pair a b
deriving (Show, Eq)
instance Bifunctor Pair where
bimap f g (Pair x y) = Pair (f x) (g y)
first f (Pair x y) = Pair (f x) y
second g (Pair x y) = Pair x (g y)
{--
(first f . second g) (Pair x y)
= first f (second g (Pair x y))
= first f (Pair x (g y))
= Pair (f x) (g y)
bimap f id (Pair x y)
= Pair (f x) (id y)
= Pair (f x) y
bimap id g (Pair x y)
= Pair (id x) (g y)
= Pair x (g y)
--}import Data.Functor.Const (Const(..))
import Data.Functor.Identity (Identity(..))
type Maybe' a = Either (Const () a) (Identity a)
toMaybe' :: Maybe a -> Maybe' a
toMaybe' Nothing = Left (Const ())
toMaybe' (Just x) = Right (Identity x)
toMaybe :: Maybe' a -> Maybe a
toMaybe (Left (Const ())) = Nothing
toMaybe (Right (Identity x)) = Just x
{--
(toMaybe' . toMaybe) (Left (Const ()))
= toMaybe' (toMaybe (Left (Const ())))
= toMaybe' Nothing
= Left (Const ())
(toMaybe' . toMaybe) (Right (Identity x))
= toMaybe' (toMaybe (Right (Identity x)))
= toMaybe' (Just x)
= Right (Identity x)
(toMaybe . toMaybe') Nothing
= toMaybe (toMaybe' Nothing)
= toMaybe (Left (Const ()))
= Nothing
(toMaybe . toMaybe') (Just x)
= toMaybe (toMaybe' (Just x))
= toMaybe (Right (Identity x))
= Just x
--}data PreList a b = Nil | Cons a b
deriving (Show, Eq)
instance Bifunctor PreList where
bimap _ _ Nil = Nil
bimap f g (Cons x y) = Cons (f x) (g y)data K2 c a b = K2 c
data Fst a b = Fst a
data Snd a b = Snd b
instance Bifunctor (K2 c) where
bimap _ _ (K2 c) = K2 c
instance Bifunctor Fst where
bimap f _ (Fst a) = Fst (f a)
instance Bifunctor Snd where
bimap _ g (Snd b) = Snd (g b)略
std::map 应该被看作一个 Profunctor(因为它类似函数类型)。